<template>
  <div style="width: 100%;">
    <el-button type="primary" @click="clickExport">{{ buttonText || '导出' }}</el-button>
  </div>
</template>
<script>
import XLSX from 'xlsx/xlsx.js'
import * as xlsxStyleObj from 'xlsx-style'

export default {
  name: 'exportExcel',
  props: {
    value: '',
    buttonText: '',
    mode: {
      default: "edit", // 编辑模式和 edit， 展示模式 view
    },
    fileName: {
      type: String,
      default: '表格'
    },
    // 表数据
    excelList: {
      type: Array,
      default: () => []
    },
    excelHeader: {
      type: Array,
      default: () => []
    },
    colWidth: null
  },
  computed: {
    isDesigner(){
      return this.$attrs._source === 'designer'
    },
    mixEditable() {
      return !(this.$attrs.readonly || this.$attrs.disabled) && this.mode === "edit"
    },
  },
  data() {
    return {
      arrayBufferContent: null
    }
  },
  created(){
  },
  watch: {
  },
  methods: {
    clickExport() {
      if (this.$listeners.buttonClick) {
        this.$listeners.buttonClick()
        return
      } else {
        this.exportData()
      }
    },
    exportData() {
      if (!Array.isArray(this.excelList) || this.excelList.length < 1) {
        this.$message({ type: 'error', message: '请选择需要导出的数据！' })
        return;
      }
      let sheetName = this.fileName
      // excel表头
      let excelHeader = this.buildHeader(this.excelHeader);
      // 头部行数，用来固定表头
      let headerRows = excelHeader.length;
      // 提取数据
      let dataList = this.extractData(this.excelList, this.excelHeader);
      excelHeader.push(...dataList, []);
      // 计算合并
      let merges = this.doMerges(excelHeader)
      // 生成sheet
      let ws = this.aoa_to_sheet(excelHeader, headerRows, this.excelHeader);
      // 单元格合并
      ws['!merges'] = merges;
      // 头部冻结
      ws["!freeze"] = {
        xSplit: "1",
        ySplit: "" + headerRows,
        topLeftCell: "B" + (headerRows + 1),
        activePane: "bottomRight",
        state: "frozen",
      };
      // 列宽
      ws['!cols'] = [];
      excelHeader.forEach(i => ws['!cols'].push({ wpx: Number(this.colWidth) || 140}))
      let workbook = {
        SheetNames: [sheetName],
        Sheets: {}
      };
      // excel样式
      workbook.Sheets[sheetName] = ws;
      console.log(workbook,'====workbook')
      let wopts = {
        bookType: 'xlsx',
        bookSST: false,
        type: 'binary',
        cellStyles: true
      };
      var generateFile = xlsxStyleObj.write(workbook, wopts);
      this.arrayBufferContent = new Blob([this.strToArrayBufferFunc(generateFile)], { type: "application/octet-stream" });
      this.openDownloadXLSXDialog(this.arrayBufferContent, sheetName + '.xlsx')
    },
    // 字符串转成ArrayBuffer
    strToArrayBufferFunc(str) {
      var buffer = new ArrayBuffer(str.length);
      var view = new Uint8Array(buffer);
      for (var i = 0; i !== str.length; ++i) view[i] = str.charCodeAt(i) & 0xff;
      return buffer;
    },

    /**
     * 构建excel表头
     * @param revealList 列表页面展示的表头
     * @returns {[]} excel表格展示的表头
     */
    buildHeader(revealList) {
      let excelHeader = [];
      // 构建生成excel表头需要的数据结构
      this.getHeader(revealList, excelHeader, 0, 0);
      // 多行表头长短不一，短的向长的看齐，不够的补上行合并占位符
      let max = Math.max(...(excelHeader.map(a => a.length)))
      excelHeader.filter(e => e.length < max).forEach(
        e => this.pushRowSpanPlaceHolder(e, max - e.length))
        console.log(excelHeader,'===buildHeader')
      return excelHeader;
    },

    /**
     * 生成头部 
     * @param headers 展示的头部
     * @param excelHeader excel头部
     * @param deep 深度
     * @param perOffset 前置偏移量
     * @returns {number}  后置偏移量
     */
    getHeader(headers, excelHeader, deep, perOffset) {
      let offset = 0
      let cur = excelHeader[deep]
      if (!cur) {
        cur = excelHeader[deep] = []
      }
      // 填充行合并占位符
      this.pushRowSpanPlaceHolder(cur, perOffset - cur.length)
      for (let i = 0; i < headers.length; i++) {
        let head = headers[i]
        cur.push(head.name)
        if (head.hasOwnProperty('child') && Array.isArray(head.child)
          && head.child.length > 0) {
          let childOffset = this.getHeader(head.child, excelHeader, deep + 1,
            cur.length - 1)
          // 填充列合并占位符
          this.pushColSpanPlaceHolder(cur, childOffset - 1)
          offset += childOffset
        } else {
          offset++
        }
      }
      return offset;

    },
    /**
    * 根据选中的数据和展示的列，生成结果
    * @param excelList
    * @param revealList
    */
    extractData(excelList, revealList) {
      // 列
      let headerList = this.flat(revealList);
      // 导出的结果集
      let excelRows = [];
      // 如果有child集合的话会用到
      let dataKeys = new Set(Object.keys(excelList[0]));
      excelList.some(e => {
        if (e.child && e.child.length > 0) {
          let childKeys = Object.keys(e.child[0]);
          for (let i = 0; i < childKeys.length; i++) {
            dataKeys.delete(childKeys[i]);
          }
          return true;
        }
      })
      this.flatData(excelList, (excelList) => {
        excelRows.push(...this.buildExcelRow(dataKeys, headerList, excelList));
      })
      return excelRows;
    },

    /**
     *
     * 
     * */
    buildExcelRow(mainKeys, headers, rawDataList) {
      // 合计行
      let sumCols = [];
      // 数据行
      let rows = [];
      for (let i = 0; i < rawDataList.length; i++) {
        let cols = []
        let rawData = rawDataList[i];
        // 提取数据
        for (let j = 0; j < headers.length; j++) {
          let header = headers[j];
          // 父元素键需要行合并
          if (rawData['rowSpan'] === 0 && mainKeys.has(header.prop)) {
            cols.push('!$ROW_SPAN_PLACEHOLDER')
          } else {
            let value;
            if (typeof header.exeFun === 'function') {
              value = header.exeFun(rawData);
            } else {
              value = rawData[header.prop];
            }
            cols.push(value)
            // 如果该列需要合计,并且是数字类型
            if (header['summable'] && typeof value === 'number') {
              sumCols[j] = (sumCols[j] ? sumCols[j] : 0) + value;
            }
          }
        }
        rows.push(cols);
      }
      // 如果有合计行
      if (sumCols.length > 0) {
        rows.push(...this.sumRowHandle(sumCols));
      }
      return rows;
    },
    sumRowHandle(sumCols) {
      //TODO
      return [];
    },
    /**
     * 合并头部单元格
     **/
    doMerges(arr) {
      // 要么横向合并 要么纵向合并
      let deep = arr.length;
      let merges = [];
      for (let y = 0; y < deep; y++) {
        // 先处理横向合并
        let row = arr[y];
        let colSpan = 0
        for (let x = 0; x < row.length; x++) {
          if (row[x] === '!$COL_SPAN_PLACEHOLDER') {
            row[x] = undefined;
            if(x+1 === row.length){
              merges.push({ s: { r: y, c: x - colSpan - 1}, e: { r: y, c: x } })
            }
            colSpan++
          } else if (colSpan > 0 && x > colSpan) {
            merges.push({ s: { r: y, c: x - colSpan - 1 }, e: { r: y, c: x - 1 } })
            colSpan = 0
          } else {
            colSpan = 0
          }
        }
      }
      // 再处理纵向合并
      let colLength = arr[0].length
      for (let x = 0; x < colLength; x++) {
        let rowSpan = 0
        for (let y = 0; y < deep; y++) {
          if (arr[y][x] === '!$ROW_SPAN_PLACEHOLDER') {
            arr[y][x] = undefined;
            rowSpan++;
            if(y+1 === deep){
              merges.push({ s: { r: y - rowSpan , c: x }, e: { r: y , c: x } })
            }
            
          } else if (rowSpan > 0 && y > rowSpan) {
            merges.push({ s: { r: y - rowSpan - 1, c: x }, e: { r: y - 1, c: x } })
            rowSpan = 0;
          } else {
            rowSpan = 0;
          }
        }
      }
      return merges;
    },

    /**
     * 从github复制过来的
     */
    aoa_to_sheet(data, headerRows, header) {
      let tableheader = []
      const formatHeader = (arr) => {
        for (let h in arr)  {
          if (arr[h].name && arr[h].prop ) {
            tableheader.push(arr[h])
          } else if (arr[h].child) {
            formatHeader(arr[h].child)
          }
        }
      }
      formatHeader(header)
      
      const ws = {};
      const range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 } };
      for (let R = 0; R !== data.length; ++R) {
        for (let C = 0; C !== data[R].length; ++C) {
          if (range.s.r > R) {
            range.s.r = R;
          }
          if (range.s.c > C) {
            range.s.c = C;
          }
          if (range.e.r < R) {
            range.e.r = R;
          }
          if (range.e.c < C) {
            range.e.c = C;
          }
          /// 这里生成cell的时候，使用上面定义的默认样式
          const cell = {
            v: data[R][C] || '',
            s: {
              font: { name: "方正楷体简体", sz: 10, color: { auto: 1 } },
              alignment: {
                /// 自动换行
                wrapText: 0,
                // 居中
                horizontal: "center",
                vertical: "center",
                indent: 0
              }
            }
          };
          // 头部列表加边框
          if (R < headerRows) {
            if (R == 0) {
              cell.s.font = { name: "方正小标宋_GBK", sz: 16, color: { auto: 1 }, bold: true, }
            } else {
              cell.s.font = { name: "方正楷体_GBK", sz: 12, color: { auto: 1 }, bold: true, }
            }
            cell.s.border = {
              top: { style: 'thin', color: { rgb: "000000" } },
              left: { style: 'thin', color: { rgb: "000000" } },
              bottom: { style: 'thin', color: { rgb: "000000" } },
              right: { style: 'thin', color: { rgb: "000000" } },
            };
            cell.s.fill = {
              patternType: 'solid',
              fgColor: { theme: 3, "tint": 0.3999755851924192, rgb: 'ffffff' },
              bgColor: { theme: 7, "tint": 0.3999755851924192, rgb: 'ffffff' }
            }
          } else {
            if (tableheader[C] && tableheader[C].position) {
              cell.s.alignment.horizontal = tableheader[C].position || 'center'
            }
          }
          const cell_ref = XLSX.utils.encode_cell({ c: C, r: R });
          if (typeof cell.v === 'number') {
            cell.t = 'n';
          } else if (typeof cell.v === 'boolean') {
            cell.t = 'b';
          } else {
            cell.t = 's';
          }
          ws[cell_ref] = cell;
        }
      }
      if (range.s.c < 10000000) {
        ws['!ref'] = XLSX.utils.encode_range(range);
      }
      return ws;
    },
    /**
     * 填充行合并占位符
     * */
    pushRowSpanPlaceHolder(arr, count) {
      for (let i = 0; i < count; i++) {
        arr.push('!$ROW_SPAN_PLACEHOLDER')
      }
    },
    // 填充列合并占位符
    pushColSpanPlaceHolder(arr, count) {
      for (let i = 0; i < count; i++) {
        arr.push('!$COL_SPAN_PLACEHOLDER')
      }
    },
    flatData(list, eachDataCallBack) {
      let resultList = [];
      for (let i = 0; i < list.length; i++) {
        let data = list[i];
        let rawDataList = [];
        // 每个子元素都父元素合并成一条数据
        if (data.child && data.child.length > 0) {
          for (let j = 0; j < data.child.length; j++) {
            delete data.child[j].bsm
            let copy = Object.assign({}, data, data.child[j]);
            rawDataList.push(copy);
            copy['rowSpan'] = (j > 0 ? 0 : data.child.length);
          }
        } else {
          data['rowSpan'] = 1;
          rawDataList.push(data);
        }
        resultList.push(...rawDataList);
        if (typeof eachDataCallBack === 'function') {
          eachDataCallBack(rawDataList)
        }
      }
      return resultList;
    }
    ,
    // 扁平头部
    flat(revealList) {
      let result = []
      revealList.forEach(e => {
        if (e.hasOwnProperty('child')) {
          result.push(...this.flat(e.child))
        } else if (e.hasOwnProperty('exeFun')) {
          result.push(e)
        } else if (e.hasOwnProperty('prop')) {
          result.push(e)
        }
      })
      return result;
    },
    s2ab(s) {
      let buf = new ArrayBuffer(s.length);
      let view = new Uint8Array(buf);
      for (let i = 0; i !== s.length; ++i) {
        view[i] = s.charCodeAt(i) & 0xFF;
      }
      return buf;
    },
    openDownloadXLSXDialog(url, saveName) {
      if (typeof url == 'object' && url instanceof Blob) {
        url = URL.createObjectURL(url); // 创建blob地址
      }
      var aLink = document.createElement('a');
      aLink.href = url;
      aLink.download = saveName || ''; // HTML5新增的属性，指定保存文件名，可以不要后缀，注意，file:///模式下不会生效
      var event;
      if (window.MouseEvent) {
        event = new MouseEvent('click');
      } else {
        event = document.createEvent('MouseEvents');
        event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false,
          false, false, false, 0, null);
      }
      aLink.dispatchEvent(event);
    }
  }
}
</script>

<style lang="scss" scoped>
.excel_container {
    display: flex;
    .left {
      flex: 1;
    }
    .el-button {
      margin-left: 5px;
    }
  }
</style>
